
package com.hlkj.pay.app.merchant.ext.impl.fuyou.utils;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.util.regex.Pattern;

import org.apache.commons.codec.digest.DigestUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.springframework.util.StringUtils;

import com.hlkj.framework.common.util.json.JsonUtils;
import com.hlkj.pay.app.merchant.ext.impl.fuyou.constant.FuYouConstant;
import com.hlkj.pay.app.merchant.ext.impl.fuyou.dto.FuYouConfig;

import lombok.extern.slf4j.Slf4j;

/**
 * Created by Ricky on 2016/11/20.
 */
@Slf4j
public class SignUtils {

    public static Map<String, Object> paraFilter(Map<String, Object> map) {

        Map<String, Object> result = new HashMap<>();

        if (map == null || map.size() <= 0) {
            return result;
        }

        for (String key : map.keySet()) {
            Object value = map.get(key);
            if (key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("key_sign") || (key.length() >= 8 && key.substring(0, 8).equalsIgnoreCase("reserved"))) {
                continue;
            }
            if (value == null || !StringUtils.hasText(value.toString())) {
                value = "";
                log.info(key + "is null," + value);
                map.put(key, value);
            }
            result.put(key, value);
        }

        return result;
    }

    public static String createLinkString(Map<String, Object> map) {

        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            Object value = map.get(key);
            if (value == null || !StringUtils.hasText(value.toString())) {
                continue;
            }
            if (i == keys.size() - 1) {
                // 拼接时，不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            }
            else {
                prestr = prestr + key + "=" + value + "&";
            }
        }

        return prestr;
    }

    public static String createNullLinkString(Map<String, Object> map) {

        List<String> keys = new ArrayList<>(map.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            Object value = map.get(key);
            if (i == keys.size() - 1) {
                // 拼接时，不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            }
            else {
                prestr = prestr + key + "=" + value + "&";
            }
        }

        return prestr;
    }

    public static void createNullLink(Map<String, String> map) {
        List<String> keys = new ArrayList<>(map.keySet());
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = map.get(key);

            if (!StringUtils.hasText(value)) {
                value = "";
                log.info(key + "is null," + value);
                map.put(key, value);
            }

        }

    }

    public static String getPaySign(Map<String, Object> params, FuYouConfig fuYouConfig)
            throws InvalidKeySpecException, SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException {
        // //移除空值
        // params.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
        // Map<String, String> mapNew = paraFilter(params);
        //
        // String preSignStr = createLinkString(mapNew);
        //
        // String sign = Sign.sign(preSignStr, fuYouConfig.getRsaPrivateKey());
        //
        // sign = sign.replace("\r\n", "");

        // 移除空值
        // params.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
        Map<String, Object> mapNew = paraFilter(params);
        String preSignStr = createNullLinkString(mapNew);
        log.info("getPaySign preSignStr:{}", preSignStr);
        String sign = Sign.sign(preSignStr, fuYouConfig.getRsaPrivateKey());
        sign = sign.replace("\r\n", "");
        return sign;
    }

    public static Boolean verifySign(Map<String, Object> map, String sign, FuYouConfig fuYouConfig) throws Exception {
        Map<String, Object> mapNew = paraFilter(map);
        String preSignStr = createNullLinkString(mapNew);
        log.info("verifySign preSignStr:{}", preSignStr);
        return Sign.verify(preSignStr.getBytes(FuYouConstant.charset), fuYouConfig.getRsaPublicKey(), sign);
    }

    public static String getSign(Object o, String key, String charset) {
        return getSignWithCase(o, key, charset, true);
    }

    /**
     *
     * @param o
     * @param key
     * @param charset
     * @param isUpperCase
     * @return
     */
    public static String getSignWithCase(Object o, String key, String charset, boolean isUpperCase) {
        String sb = getSignContent(o);
        String result = sb.toString();
        result += "key=" + key;
        System.out.println(result);
        // result = MD5.MD5Encode(result, charset);
        result = DigestUtils.md5Hex(result.getBytes(Charset.forName(charset)));
        return isUpperCase ? result.toUpperCase() : result.toLowerCase();
    }

    /**
     * @param o
     * @return
     */
    public static String getSignContent(Object o) {
        ArrayList<String> list = new ArrayList<String>();
        StringBuilder sb = new StringBuilder();
        try {
            BeanInfo intro = Introspector.getBeanInfo(o.getClass(), Object.class);
            for (PropertyDescriptor f : intro.getPropertyDescriptors()) {
                System.out.println(f.getReadMethod().getName());
                if (!"sign".equals(f.getName()) && !"sign_type".equals(f.getName()) && f.getReadMethod().invoke(o) != null
                        && StringUtils.hasText(f.getReadMethod().invoke(o).toString())) {
                    list.add(f.getName() + "=" + f.getReadMethod().invoke(o).toString() + "&");
                }
            }
            int size = list.size();
            String[] arrayToSort = list.toArray(new String[size]);
            Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);

            for (int i = 0; i < size; i++) {
                sb.append(arrayToSort[i]);
            }
            System.out.println("signContent:" + sb);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        finally {
            Introspector.flushFromCaches(o.getClass());
        }
        return sb.toString();
    }

    public static String getMerchantSign(Map<String, Object> params, FuYouConfig fuYouConfig)
            throws InvalidKeySpecException, SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException {
        // 移除空值
        params.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
        Map<String, Object> mapNew = paraFilter(params);
        String preSignStr = createLinkString(mapNew);
        preSignStr = preSignStr + "&key=" + fuYouConfig.getMd5Key();
        log.info("getMerchantSign preSignStr:{}", preSignStr);
        return md5(preSignStr);
    }

    public static String getMerchantNullSign(Map<String, Object> params, FuYouConfig fuYouConfig)
            throws InvalidKeySpecException, SignatureException, NoSuchAlgorithmException, InvalidKeyException, IOException {
        // 移除空值
        params.entrySet().removeIf(entry -> Objects.isNull(entry.getValue()));
        Map<String, Object> mapNew = paraFilter(params);
        String preSignStr = createNullLinkString(mapNew);
        preSignStr = preSignStr + "&key=" + fuYouConfig.getMd5Key();
        log.info("getMerchantNullSign preSignStr:{}", preSignStr);
        return md5(preSignStr);
    }

    public static Map<String, Object> xmlToObject(String xmlStr) {
        String decodedXML;
        if (xmlStr.startsWith("<?xml")) {
            log.debug("富友返回参数 以xml开头无需转换 decodedXML:{}", xmlStr);
            decodedXML = xmlStr;
        }
        else {
            try {
                decodedXML = URLDecoder.decode(xmlStr, FuYouConstant.charset); // 使用 UTF-8 编码，或根据 FuYouConstant.charset 设置
            }
            catch (Exception e) {
                return new HashMap<>();
            }
        }
        Map<String, Object> map = new HashMap<>();
        Document doc;
        try {
            doc = DocumentHelper.parseText(decodedXML); // 解析 XML 字符串为 Document
            Element root = doc.getRootElement(); // 获取根节点
            map = parseElementToMap(root); // 解析根节点及其子元素
        }
        catch (DocumentException e) {
            e.printStackTrace();
        }

        return map;
    }

    // 递归地解析每个元素及其子元素
    private static Map<String, Object> parseElementToMap(Element element) {
        Map<String, Object> elementMap = new HashMap<>();

        List<Element> children = element.elements(); // 获取当前元素的所有子元素

        // 如果有子元素，遍历它们
        if (children != null && !children.isEmpty()) {
            for (Element child : children) {
                String childName = child.getName();
                List<Element> childElements = child.elements(); // 获取该子元素下的所有子节点

                // 如果有多个相同的子元素，则处理为列表
                if (childElements != null && !childElements.isEmpty()) {
                    List<Object> childValues = new ArrayList<>();
                    for (Element e : childElements) {
                        // 递归解析嵌套的 XML
                        childValues.add(parseElementToMap(e));
                    }
                    elementMap.put(childName, childValues); // 将该节点的多个值（作为对象列表）放入 Map 中
                }
                else {
                    // 如果没有嵌套元素，直接存储它的文本值
                    elementMap.put(childName, child.getStringValue().trim());
                }
            }
        }

        return elementMap;
    }

    public static Map<String, Object> callBackXmlToObject(String xmlStr) {
        String decodedXML;
        if (xmlStr.startsWith("<?xml")) {
            log.info("富友回调参数 以xml开头无需转换 decodedXML:{}", xmlStr);
            decodedXML = xmlStr;
        }
        else {
            try {
                decodedXML = URLDecoder.decode(xmlStr, FuYouConstant.charset);
            }
            catch (Exception e) {
                return new HashMap<>();
            }
            if (!decodedXML.startsWith("<?xml")) {
                log.info("富友回调参数 数据decode后不是 <?xml 开头 继续尝试 decode decodedXML:{}", decodedXML);
                try {
                    decodedXML = URLDecoder.decode(decodedXML, FuYouConstant.charset);
                }
                catch (Exception e) {
                    log.info("富友回调二次数据decode失败 放弃decode decodedXML:{},message", decodedXML, e.getMessage());
                }
            }
        }
        Map<String, Object> map = new HashMap<>();
        Document doc;
        log.info("富友回调 decodedXML:{}", decodedXML);
        try {
            doc = DocumentHelper.parseText(decodedXML); // 解析 XML 字符串为 Document
            Element root = doc.getRootElement(); // 获取根节点
            map = parseElementToMap(root); // 解析根节点及其子元素
        }
        catch (DocumentException e) {
            e.printStackTrace();
        }

        return map;
    }

    public static String convertMapToXml(Map<String, Object> map) {
        Document document = DocumentHelper.createDocument();
        Element rootElement = document.addElement("xml");
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            if (entry.getValue() == null) {
                continue;
            }
            Element element = rootElement.addElement(entry.getKey());
            element.setText(entry.getValue().toString());
        }
        // 将XML字符串返回
        return "<?xml version=\"1.0\" encoding=\"GBK\" standalone=\"yes\"?>" + document.getRootElement().asXML();
    }

    public static void encodeParams(Map<String, String> params) {
        params.forEach((key, value) -> {
            if (StringUtils.hasText(value)) {
                if (containsChinese(value)) {
                    try {
                        params.put(key, URLEncoder.encode(value, FuYouConstant.charset));
                    }
                    catch (UnsupportedEncodingException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });

    }

    // 判断字符串是否包含中文
    public static boolean containsChinese(String str) {
        String regex = "[\u4e00-\u9fa5]";
        Pattern pattern = Pattern.compile(regex);
        return pattern.matcher(str).find();
    }

    public static String md5(String string) {
        byte[] hash;
        try {
            hash = MessageDigest.getInstance("MD5").digest(string.getBytes(FuYouConstant.charset));
        }
        catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Huh, MD5 should be supported?", e);
        }
        catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Huh, UTF-8 should be supported?", e);
        }

        StringBuilder hex = new StringBuilder(hash.length * 2);
        for (byte b : hash) {
            if ((b & 0xFF) < 0x10)
                hex.append("0");
            hex.append(Integer.toHexString(b & 0xFF));
        }
        return hex.toString();
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        String xml="<?xml version=\"1.0\" encoding=\"GBK\" standalone=\"yes\"?>\n" +
                "<xml>\n" +
                "    <count>4</count>\n" +
                "    <ins_cd>08M0029820</ins_cd>\n" +
                "    <mchntList>\n" +
                "        <mchnt>\n" +
                "            <addn_fee>0</addn_fee>\n" +
                "            <d1_fee>0</d1_fee>\n" +
                "            <mchnt_cd>0005810F5349183</mchnt_cd>\n" +
                "            <pay_amt>1</pay_amt>\n" +
                "            <pay_st>2</pay_st>\n" +
                "            <pay_st_desc>失败</pay_st_desc>\n" +
                "            <pay_st_err_desc>此卡已挂失，请与发卡方联系[609X1000041]</pay_st_err_desc>\n" +
                "            <pay_ts></pay_ts>\n" +
                "            <pay_txn_cd>9004</pay_txn_cd>\n" +
                "            <repay_dt></repay_dt>\n" +
                "            <repay_tag>1</repay_tag>\n" +
                "            <reserved_chnl_pay_ssn>330308960820</reserved_chnl_pay_ssn>\n" +
                "            <reserved_orig_pay_ssn>330308314029</reserved_orig_pay_ssn>\n" +
                "            <reserved_orig_settle_dt>20230107</reserved_orig_settle_dt>\n" +
                "            <reserved_repay_ssn></reserved_repay_ssn>\n" +
                "            <settle_acnt_nm>程方云</settle_acnt_nm>\n" +
                "            <settle_acnt_no>621661***1388</settle_acnt_no>\n" +
                "            <settle_dt>20230109</settle_dt>\n" +
                "        </mchnt>\n" +
                "        <mchnt>\n" +
                "            <addn_fee>0</addn_fee>\n" +
                "            <d1_fee>0</d1_fee>\n" +
                "            <mchnt_cd>0005810F5349183</mchnt_cd>\n" +
                "            <pay_amt>10</pay_amt>\n" +
                "            <pay_st>2</pay_st>\n" +
                "            <pay_st_desc>失败</pay_st_desc>\n" +
                "            <pay_st_err_desc>此卡已挂失，请与发卡方联系[609X1000041]</pay_st_err_desc>\n" +
                "            <pay_ts></pay_ts>\n" +
                "            <pay_txn_cd>9004</pay_txn_cd>\n" +
                "            <repay_dt></repay_dt>\n" +
                "            <repay_tag>1</repay_tag>\n" +
                "            <reserved_chnl_pay_ssn>330308960978</reserved_chnl_pay_ssn>\n" +
                "            <reserved_orig_pay_ssn>330308585652</reserved_orig_pay_ssn>\n" +
                "            <reserved_orig_settle_dt>20230108</reserved_orig_settle_dt>\n" +
                "            <reserved_repay_ssn></reserved_repay_ssn>\n" +
                "            <settle_acnt_nm>程方云</settle_acnt_nm>\n" +
                "            <settle_acnt_no>621661***1388</settle_acnt_no>\n" +
                "            <settle_dt>20230109</settle_dt>\n" +
                "        </mchnt>\n" +
                "        <mchnt>\n" +
                "            <addn_fee>0</addn_fee>\n" +
                "            <d1_fee>0</d1_fee>\n" +
                "            <mchnt_cd>0005810F5349183</mchnt_cd>\n" +
                "            <pay_amt>10</pay_amt>\n" +
                "            <pay_st>6</pay_st>\n" +
                "            <pay_st_desc>退票</pay_st_desc>\n" +
                "            <pay_st_err_desc></pay_st_err_desc>\n" +
                "            <pay_ts>20230110025543</pay_ts>\n" +
                "            <pay_txn_cd>9004</pay_txn_cd>\n" +
                "            <repay_dt>20230109</repay_dt>\n" +
                "            <repay_tag></repay_tag>\n" +
                "            <reserved_chnl_pay_ssn>330308585652</reserved_chnl_pay_ssn>\n" +
                "            <reserved_orig_pay_ssn></reserved_orig_pay_ssn>\n" +
                "            <reserved_orig_settle_dt></reserved_orig_settle_dt>\n" +
                "            <reserved_repay_ssn>330308960978</reserved_repay_ssn>\n" +
                "            <settle_acnt_nm>程方云</settle_acnt_nm>\n" +
                "            <settle_acnt_no>621661***1388</settle_acnt_no>\n" +
                "            <settle_dt>20230108</settle_dt>\n" +
                "        </mchnt>\n" +
                "        <mchnt>\n" +
                "            <addn_fee>0</addn_fee>\n" +
                "            <d1_fee>0</d1_fee>\n" +
                "            <mchnt_cd>0005810F5349183</mchnt_cd>\n" +
                "            <pay_amt>1</pay_amt>\n" +
                "            <pay_st>6</pay_st>\n" +
                "            <pay_st_desc>退票</pay_st_desc>\n" +
                "            <pay_st_err_desc></pay_st_err_desc>\n" +
                "            <pay_ts>20230110025543</pay_ts>\n" +
                "            <pay_txn_cd>9004</pay_txn_cd>\n" +
                "            <repay_dt>20230109</repay_dt>\n" +
                "            <repay_tag></repay_tag>\n" +
                "            <reserved_chnl_pay_ssn>330308314029</reserved_chnl_pay_ssn>\n" +
                "            <reserved_orig_pay_ssn></reserved_orig_pay_ssn>\n" +
                "            <reserved_orig_settle_dt></reserved_orig_settle_dt>\n" +
                "            <reserved_repay_ssn>330308960820</reserved_repay_ssn>\n" +
                "            <settle_acnt_nm>程方云</settle_acnt_nm>\n" +
                "            <settle_acnt_no>621661***1388</settle_acnt_no>\n" +
                "            <settle_dt>20230107</settle_dt>\n" +
                "        </mchnt>\n" +
                "    </mchntList>\n" +
                "    <random_str>LTMTBRUXNIW9PZ0X4OEH5JSDDE5R5VLD</random_str>\n" +
                "    <reserved_return_code>000000</reserved_return_code>\n" +
                "    <reserved_return_msg>SUCCESS</reserved_return_msg>\n" +
                "    <sign>R32Edx2y3l8yeQHEVcdhpw4O3B1tlrERboEQfBWqdq/hlswKK5pki1M9LeynUBqeNocTTehce9Zkka/jR4sgXxnVv3iYICNBRy4f04frPWMfVXZqsA/bVMLEEAggUxkHK7T1k+ULFsGDXjO1exbt7WHoqDq202HSWcqE+807Gb8=</sign>\n" +
                "</xml>";
        System.out.println(JsonUtils.toJsonString(xmlToObject(xml)));
    }

}
